浅析Linux 64位系统虚拟地址和物理地址的映射及验证方法

您所在的位置:网站首页 linux 内存地址映射 浅析Linux 64位系统虚拟地址和物理地址的映射及验证方法

浅析Linux 64位系统虚拟地址和物理地址的映射及验证方法

2024-05-14 16:09| 来源: 网络整理| 查看: 265

虚拟内存

先简单介绍一下操作系统中为什么会有虚拟地址和物理地址的区别。因为Linux中有进程的概念,那么每个进程都有自己的独立的地址空间。

现在的操作系统都是64bit的,也就是说如果在用户态的进程中创建一个64位的指针,那么在这个进程中,这个指针能够指向的范围是0~0xFFFFFFFFFFFFFFFF(总共有16个F,每个F是4个bit)。

每个进程“理论上”都有这样的地址范围(-,-这里的”理论“是指猜测一下,指针乱指向未定义的范围会引发段错误,下文中会写明64bit的用户空间的地址范围)。

我们看到了,Linux为了让每个进程空间的独立,创造了虚拟地址这个概念。但是计算机最终还是需要操作物理的内存的。

那么虚拟地址和物理地址的映射关系是怎样的?也只能用映射表了。比如说:进程A虚拟空间中的第0x1234个字节,对应于物理内存中的第0x823ABC个字节。这个一个字节和一个字节对应,理论上是可以的,但是太消耗资源了,为了映射这“一个字节”,仅映射这“一个字节”的表项的大小也远超过了一个字节的大小(大约四十个字节左右)。这是不行的,这就像几十个产品和项目经理去管一个程序员工作,这是效率低下的。

所以页这个概念产生了,一个页一个页映射总还可以了吧,我们将页作为最小单位去映射就好了。大多数32位体系结构支持4KB的页,而64位体系结构一般会支持8KB的页。在linux使用命令获取当前系统的页大小:

getconf PAGE_SIZE

在我的ubuntu 16.04 x86_64上的系统得到的结果是 4096。目前大部分64位的系统的页大小都是4096个字节。

系统中每个物理页都会建立一个类似映射表的结构体,但是依然会有人觉得这有点浪费内存。我们来算一下,比如一个物理页的属性和映射表的内容占用40个字节(linux代码中是struct page)。假设如当前大部分Linux上的页为4KB大小,系统有4GB物理内存,那么就有1048576个页,这么多页的映射表消耗的内存是1048576 * 40byte = 40MB。用40MB去管理4GB,还是可以接受的。

64位系统的虚拟内存布局

在AArch64下,页大小为4KB时,页管理为四级架构时的Linux的进程中的虚拟内存布局如下:

可以看到即使是虚拟地址,用户态下能用的地址也就只是0 ~ 0000ffffffffffff,不过也有256TB大小了。也就是说每个进程都有自己独立的0 ~ 0000ffffffffffff的地址空间。0x0000ffffffffffff是12个f,也就是48个bit。

每个进程都有自己的虚拟地址到物理地址的映射关系表。Linux内核会根据每个不同的进程去查找表:如进程A的虚拟空间地址K的物理地址是哪个。为了加快查找效率,虚拟内存的地址的不同段映射到了不同的entry上,页管理表有4级的也有3级的。最常用的4级页管理映射表如下:

可以看到[47:0]这48个bits的虚拟地址,被分成了五段,前四段的每一份长度都是9 bits,最后一段是12 bits。

每个9 bits的段都是2^9 = 512,也就是说每个分级段都有512个entry。

最后一段[11:0],大小是12 bits的即2^12 = 4096,4096就是一个页的大小,所以最后一段是页内偏移(因为映射是以页为单位,所以虚拟地址和物理地址的页内偏移都是一样的)。前四段合在一起就是虚拟页号。

我们举一个48 bit 虚拟地址的例子,这个地址以八进制表示:

003 010 007 413 1056

上面所述的每个Entry的结构体如下:

可以看到物理地址的页号是40 bits,也就是说最多有2^40个物理页,每个页是4096个字节,也就是最多4PB(4096TB)。

虚拟地址到物理地址的验证方法

说了这么多,如何验证上面说的这些是真的。就算推导出物理地址了,那又有啥用呢?

如果你知道共享库和静态库的区别的话,那么就会知道不同的进程如果用了同一个共享库,那么其实这两个不同的进程使用的共享库是指向同一个物理地址!如果能验证这一点,那么从虚拟地址推导到物理地址的方法大体是正确的,以上所述大体也是对的。

借助proc下的maps和pagemap

通过man命令

man proc

可以找到以下条目: 以上我们知道通过/proc/[pid]/maps就能够知道一个进程的虚拟地址。 以上我们知道通过/proc/[pid]/pagemap就能够将一个进程的虚拟地址页转成物理地址页。

测试代码

下面上硬菜。小伙子你要讲武德,你不能闪!

代码如下:

#include #include #include #include #include size_t virtual_to_physical(pid_t pid, size_t addr) { char str[20]; sprintf(str, "/proc/%u/pagemap", pid); int fd = open(str, O_RDONLY); if(fd < 0) { printf("open %s failed!\n", str); return 0; } size_t pagesize = getpagesize(); size_t offset = (addr / pagesize) * sizeof(uint64_t); if(lseek(fd, offset, SEEK_SET) < 0) { printf("lseek() failed!\n"); close(fd); return 0; } uint64_t info; if(read(fd, &info, sizeof(uint64_t)) != sizeof(uint64_t)) { printf("read() failed!\n"); close(fd); return 0; } if((info & (((uint64_t)1)


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3